using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using FireStarter.Base;
using FireStarterModeller.BO;
using Microsoft.CSharp;

namespace FireStarter.CodeGeneration
{
    internal class CodeDomBoClass : CodeDomBoClassStub
    {
        public CodeDomBoClass(CodeDomProvider codeDomProvider, IModelClass dmClass)
            : base(codeDomProvider, dmClass)
        {
        }

        protected override CodeCompileUnit GetCodeCompileUnit()
        {
            return GetClassDefPartialCompileUnit();
        }

        private CodeCompileUnit GetClassDefPartialCompileUnit()
        {
            CodeNamespace classNamespace;
            CodeTypeDeclaration classType;
            CodeCompileUnit classCode = GetClassStubPartialCompileUnit(
                " NB Custom code should be placed in the provided stub class." +
                Environment.NewLine + " Please do not modify this class directly!",
                true, out classNamespace, out classType);

            //Properties
            CodeTypeMember firstMember = null;
            CodeTypeMember member = null;

            foreach (IModelProperty property in _modelClass.Properties)
            {
                if (property.GenerateCode)
                {
                    member = GetPropertyDefMemberProperty(classNamespace, property);
                    if (firstMember == null)
                    {
                        firstMember = member;
                    }
                    classType.Members.Add(member);
                }
            }
            CreateRegion(firstMember, member, "Properties");

            //Relationships
            firstMember = null;
            member = null;
            foreach (IModelRelationship relationship in _modelClass.Relationships)
            {
                if (relationship.GenerateCode)
                {
                    member = GetRelationshipDefMemberProperty(classNamespace, relationship);
                    if (firstMember == null)
                    {
                        firstMember = member;
                    }
                    classType.Members.Add(member);
                }
            }
            CreateRegion(firstMember, member, "Relationships");

            ////Find by Properties
            //AddFindByClass(classNamespace, classType, defClass);

            return classCode;
        }

        private CodeMemberProperty GetPropertyDefMemberProperty(CodeNamespace classNamespace, IModelProperty property)
        {
            CodeMemberProperty memberProperty = new CodeMemberProperty();
            memberProperty.Name = property.PropertyName;
            IModelPropertyType modelPropertyType = property.PropertyType;

            CodeTypeReference reference;
            if (modelPropertyType != null)
            {
                string typeName = modelPropertyType.PropertyTypeName;
                string assemblyName = modelPropertyType.AssemblyName ?? "";
                EnsureAssemblyImported(classNamespace, assemblyName);
                reference = new CodeTypeReference(typeName);
                if (modelPropertyType.IsValueType)
                {
                    if (_codeDomProvider is CSharpCodeProvider)
                    {
                        reference = new CodeTypeReference(typeName + "?");
                    }
                    else
                    {
                        reference = new CodeTypeReference("Nullable", reference);
                    }
                }
            }
            else
            {
                reference = new CodeTypeReference(typeof(object));
            }
            memberProperty.Type = reference;
            memberProperty.HasGet = true;
            memberProperty.Attributes = MemberAttributes.Public;
            CodeMethodReturnStatement returnStatement = new CodeMethodReturnStatement(
                new CodeCastExpression(reference, new CodeMethodInvokeExpression(
                                                      new CodeBaseReferenceExpression(), "GetPropertyValue", new CodePrimitiveExpression(property.PropertyName))));
            memberProperty.GetStatements.Add(returnStatement);

            if (property.HasSetter)
            {
                memberProperty.HasSet = true;
                memberProperty.SetStatements.Add(new CodeMethodInvokeExpression(
                                                     new CodeBaseReferenceExpression(), "SetPropertyValue", new CodePrimitiveExpression(property.PropertyName), new CodePropertySetValueReferenceExpression()));
            }
            else
            {
                memberProperty.HasSet = false;
            }
            return memberProperty;
        }

        private static CodeMemberProperty GetRelationshipDefMemberProperty(CodeNamespace classNamespace, IModelRelationship relationship)
        {
            CodeMemberProperty memberProperty = new CodeMemberProperty();
            memberProperty.Name = relationship.RelationshipName;
            //DefClass relatedClass = (DefClass)relationship.ClassName.MyRelatedClass.CurrentClass;
            string typeName = relationship.RelatedClass;
            string assemblyName = relationship.RelatedAssembly;
            if (String.IsNullOrEmpty(typeName))
            {
                typeName = "BusinessObject";
                assemblyName = "Habanero.BO";
            }
            if (!string.IsNullOrEmpty(assemblyName))
            {
                EnsureAssemblyImported(classNamespace, assemblyName);
            }
            CodeTypeReference reference = new CodeTypeReference(typeName);
            CodeTypeReference returnType;
            if (relationship.IsMultiple)
            {
                returnType = new CodeTypeReference("BusinessObjectCollection", reference);
            }
            else
            {
                returnType = reference;
            }
            memberProperty.Type = returnType;

            memberProperty.HasGet = true;
            memberProperty.Attributes = MemberAttributes.Public;
            CodeMethodReturnStatement returnStatement;
            if (relationship.IsMultiple)
            {
                returnStatement = new CodeMethodReturnStatement(
                    new CodeMethodInvokeExpression(
                        new CodeMethodReferenceExpression(
                            new CodeVariableReferenceExpression("Relationships"),
                            "GetRelatedCollection", reference),
                        new CodePrimitiveExpression(relationship.RelationshipName)));
                //Generated Code sample: return Relationships.GetRelatedCollection<Contact>("Contacts");
            }
            else
            {
                returnStatement = new CodeMethodReturnStatement(
                    new CodeMethodInvokeExpression(
                        new CodeMethodReferenceExpression(
                            new CodeVariableReferenceExpression("Relationships"),
                            "GetRelatedObject", reference),
                        new CodePrimitiveExpression(relationship.RelationshipName)));
                //Generated Code sample: return Relationships.GetRelatedObject<Contact>("Contact");
            }
            memberProperty.GetStatements.Add(returnStatement);

            if (relationship.IsMultiple)
            {
                memberProperty.HasSet = false;
            }
            else
            {
                memberProperty.HasSet = true;
                CodeMethodInvokeExpression setStatement = new CodeMethodInvokeExpression(
                    new CodeMethodReferenceExpression(
                        new CodeVariableReferenceExpression("Relationships"), "SetRelatedObject"),
                    new CodePrimitiveExpression(relationship.RelationshipName),
                    new CodePropertySetValueReferenceExpression());
                memberProperty.SetStatements.Add(setStatement);
                //Generated Code sample: Relationships.SetRelatedObject("Contact", value);
            }

            return memberProperty;
        }


    }
}